package com.lyncs.ods.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.lyncs.ods.common.api.CommonPage;
import com.lyncs.ods.common.exception.ApiException;
import com.lyncs.ods.common.web.RequestHolder;
import com.lyncs.ods.constant.LanguageConstant;
import com.lyncs.ods.constant.LyncsOdsConstant;
import com.lyncs.ods.modules.company.model.CompanyContactInfo;
import com.lyncs.ods.modules.company.model.CompanyInfo;
import com.lyncs.ods.modules.setting.service.CmsTextService;
import com.lyncs.ods.modules.txn.model.*;
import com.lyncs.ods.modules.user.model.CompanyUserRelation;
import com.lyncs.ods.req.ListPageSearchReq;
import com.lyncs.ods.req.SaveDeliverReq;
import com.lyncs.ods.req.SaveOrderReq;
import com.lyncs.ods.req.SaveSettlementReq;
import com.lyncs.ods.resp.CompanyShortInfoResp;
import com.lyncs.ods.resp.ListPageResp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.beans.BeanUtils;

import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author alex
 * @date 2022/2/27 15:14
 * @description
 */
@Slf4j
public class TxnCommonUtils {

    public static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy/MM/dd");
    public static final DateTimeFormatter DTF1 = DateTimeFormatter.ofPattern("yyMMdd");
    /**
     * 单据名称前缀
     */
    public static final String TICKET_NAME_PREFIX = "000000";
    /**
     * 长度补齐位数
     */
    public static final Integer TICKET_COUNT_LENGTH = 6;

    /**
     * 获取买卖方公司名称
     *
     * @param contactFunc contactFunc
     * @param list        txn infos
     * @return company name map
     */
    public static <T extends TxnCommonInfo> Map<String, String> getTxnCompanyName(Function<List<T>, Map<String, String>> contactFunc, @NotNull List<T> list) {
        return contactFunc.apply(list);
    }

    /**
     * 获取差异sku
     *
     * @param skuInfoList     order request sku infos
     * @param existSkuDetails order sku infos from database
     * @param revertSkus      revert sku ids
     * @return L:insert list;M:delete id list;R:update list
     */
    public static Triple<List<TxnSkuDetail>, List<Long>, List<Pair<TxnSkuDetail, TxnSkuDetail>>> getOrderDiffSkus(Long orderId, List<SaveOrderReq.OrderSkuInfo> skuInfoList, List<TxnSkuDetail> existSkuDetails, List<TxnSkuDetail> revertSkus) {
        List<TxnSkuDetail> skuInsertList = new ArrayList<>();
        List<Long> skuDelList = new ArrayList<>();
        List<Pair<TxnSkuDetail, TxnSkuDetail>> skuUpdateList = new ArrayList<>();

        Map<Long, TxnSkuDetail> existSkuMap = existSkuDetails.stream().collect(Collectors.toMap(TxnSkuDetail::getSkuId, o -> o));
        Map<Long, SaveOrderReq.OrderSkuInfo> reqSkuMap = skuInfoList.stream().collect(Collectors.toMap(SaveOrderReq.OrderSkuInfo::getSkuId, o -> o));
        List<Long> reqSkuIds = new ArrayList<>(reqSkuMap.keySet());
        List<Long> existSkuIds = new ArrayList<>(existSkuMap.keySet());
        List<Long> allSkuIds = CollectionUtil.addAllIfNotContains(reqSkuIds, existSkuIds);
        for (Long skuId : allSkuIds) {
            TxnSkuDetail existSku = existSkuMap.get(skuId);
            SaveOrderReq.OrderSkuInfo reqSkuInfo = reqSkuMap.get(skuId);

            if (existSku != null && reqSkuInfo != null) {
                TxnSkuDetail newDetail = new TxnSkuDetail();
                BeanUtils.copyProperties(existSku, newDetail);
                //update
                newDetail.setTotalCount(reqSkuInfo.getCount())
                        .setAmount(reqSkuInfo.getAmount())
                        .setTotalAmount(reqSkuInfo.getAmount().multiply(reqSkuInfo.getCount()))
                        .setRemark(reqSkuInfo.getRemark())
                        .setLastUpdater(RequestHolder.getUserId());
                if (LyncsOdsConstant.TxnSkuStatus.DELETED.getKey().equals(existSku.getStatus())) {
                    newDetail.setStatus(LyncsOdsConstant.TxnSkuStatus.INIT.getKey())
                            .setDeliveredCount(BigDecimal.ZERO);
                    revertSkus.add(newDetail);
                } else {
                    skuUpdateList.add(Pair.of(existSku, newDetail));
                }

            } else if (existSku != null) {
                //delete
                skuDelList.add(skuId);
            } else {
                //insert
                skuInsertList.add(
                        new TxnSkuDetail().setId(IdUtil.getSnowflakeNextId())
                                .setOrderId(orderId)
                                .setSkuId(reqSkuInfo.getSkuId())
                                .setTotalCount(reqSkuInfo.getCount())
                                .setDeliveredCount(BigDecimal.ZERO)
                                .setAmount(reqSkuInfo.getAmount())
                                .setTotalAmount(reqSkuInfo.getAmount().multiply(reqSkuInfo.getCount()))
                                .setStartTime(RequestHolder.getRequestDateTime())
                                .setStatus(LyncsOdsConstant.OrderStatus.INIT.getKey())
                                .setRemark(reqSkuInfo.getRemark())
                                .setLastUpdater(RequestHolder.getUserId())
                                .setCreateTime(RequestHolder.getRequestDateTime())
                                .setUpdateTime(RequestHolder.getRequestDateTime())
                                .setCreator(RequestHolder.getUserId())
                                .setCreatorCompanyId(RequestHolder.getCompanyId())
                );
            }
        }
        return Triple.of(skuInsertList, skuDelList, skuUpdateList);
    }

    /**
     * 获取差异sku
     *
     * @param skuInfoList      deliver request sku infos
     * @param existDeliverSkus deliver sku infos from database
     * @return L:insert list;M:delete id list;R:update list
     */
    public static Triple<List<TxnSkuDetail>, List<Long>, List<Pair<TxnSkuDetail, TxnSkuDetail>>> getDeliverDiffSkus(Long deliverId, List<SaveDeliverReq.DeliverSkuInfo> skuInfoList, List<TxnSkuDetail> existDeliverSkus, List<Triple<Long, Long, BigDecimal>> orderSkuCountList) {
        Triple<List<TxnSkuDetail>, List<Long>, List<Pair<TxnSkuDetail, TxnSkuDetail>>> triple = Triple.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
        Map<Boolean, Map<String, SaveDeliverReq.DeliverSkuInfo>> newSkuMap = skuInfoList.stream().collect(Collectors.groupingBy(sku -> Objects.nonNull(sku.getOrderId()), Collectors.toMap(sku -> buildLong2StringKeyAllowNull(sku.getOrderId(), sku.getSkuId()), o -> o)));
        Map<Boolean, Map<String, TxnSkuDetail>> oldSkuMap = existDeliverSkus.stream().collect(Collectors.groupingBy(sku -> Objects.nonNull(sku.getOrderId()), Collectors.toMap(txn -> buildLong2StringKeyAllowNull(txn.getOrderId(), txn.getSkuId()), o -> o)));

        //关联订单的交付单sku差异信息
        orderSkuCountList.addAll(getDeliverDiffSkus(deliverId, newSkuMap.get(Boolean.TRUE), oldSkuMap.get(Boolean.TRUE), triple));
        //未关联订单的交付单sku差异信息
        getDeliverDiffSkus(deliverId, newSkuMap.get(Boolean.FALSE), oldSkuMap.get(Boolean.FALSE), triple);
        return triple;
    }

    private static List<Triple<Long, Long, BigDecimal>> getDeliverDiffSkus(Long deliverId, Map<String, SaveDeliverReq.DeliverSkuInfo> newSkuMap, Map<String, TxnSkuDetail> oldSkuMap, Triple<List<TxnSkuDetail>, List<Long>, List<Pair<TxnSkuDetail, TxnSkuDetail>>> triple) {
        oldSkuMap = CollectionUtil.isEmpty(oldSkuMap) ? new HashMap<>() : oldSkuMap;
        newSkuMap = CollectionUtil.isEmpty(newSkuMap) ? new HashMap<>() : newSkuMap;
        List<String> existOrderSkuIds = new ArrayList<>(oldSkuMap.keySet());
        List<String> reqSkuIds = new ArrayList<>(newSkuMap.keySet());
        List<String> allOrderSkuIds = CollectionUtil.addAllIfNotContains(reqSkuIds, existOrderSkuIds);
        List<Triple<Long, Long, BigDecimal>> orderSkuCountList = new ArrayList<>();
        for (String orderSkuId : allOrderSkuIds) {
            TxnSkuDetail existSku = oldSkuMap.get(orderSkuId);
            SaveDeliverReq.DeliverSkuInfo reqSkuInfo = newSkuMap.get(orderSkuId);
            BigDecimal deliverCount = reqSkuInfo == null ? null : reqSkuInfo.getDeliverCount();
            if (existSku != null && reqSkuInfo != null) {
                TxnSkuDetail newDetail = new TxnSkuDetail();
                BeanUtils.copyProperties(existSku, newDetail);
                //update
                newDetail.setTotalCount(deliverCount)
                        .setDeliveredCount(deliverCount)
                        .setLastUpdater(RequestHolder.getUserId())
                        .setAmount(reqSkuInfo.getAmount())
                        .setTotalAmount(deliverCount.multiply(reqSkuInfo.getAmount()))
                        .setRemark(reqSkuInfo.getRemark());
                triple.getRight().add(Pair.of(existSku, newDetail));
                if (Objects.nonNull(existSku.getOrderId())) {
                    orderSkuCountList.add(Triple.of(existSku.getOrderId(), existSku.getSkuId(), deliverCount.subtract(existSku.getDeliveredCount())));
                }
            } else if (existSku != null) {
                deliverCount = BigDecimal.ZERO.subtract(existSku.getDeliveredCount());
                //delete
                triple.getMiddle().add(existSku.getSkuId());
                if (Objects.nonNull(existSku.getOrderId())) {
                    orderSkuCountList.add(Triple.of(existSku.getOrderId(), existSku.getSkuId(), deliverCount));
                }
            } else if (reqSkuInfo != null) {
                //insert
                triple.getLeft().add(new TxnSkuDetail()
                        .setId(IdUtil.getSnowflakeNextId())
                        .setDeliverId(deliverId)
                        .setOrderId(reqSkuInfo.getOrderId())
                        .setSkuId(reqSkuInfo.getSkuId())
                        .setTotalCount(reqSkuInfo.getDeliverCount())
                        .setDeliveredCount(reqSkuInfo.getDeliverCount())
                        .setAmount(reqSkuInfo.getAmount())
                        .setTotalAmount(reqSkuInfo.getAmount().multiply(reqSkuInfo.getDeliverCount()))
                        .setStartTime(RequestHolder.getRequestDateTime())
                        .setStatus(LyncsOdsConstant.TxnSkuStatus.DELIVERED.getKey())
                        .setRemark(reqSkuInfo.getRemark())
                        .setCreator(RequestHolder.getUserId())
                        .setLastUpdater(RequestHolder.getUserId())
                        .setCreateTime(RequestHolder.getRequestDateTime())
                        .setUpdateTime(RequestHolder.getRequestDateTime())
                        .setCreatorCompanyId(RequestHolder.getCompanyId())
                );
                if (Objects.nonNull(reqSkuInfo.getOrderId())) {
                    orderSkuCountList.add(Triple.of(reqSkuInfo.getOrderId(), reqSkuInfo.getSkuId(), deliverCount));
                }
            }
        }
        return orderSkuCountList;
    }

    public static <T extends TxnCommonInfo> CommonPage<ListPageResp> getListPageRespData(ListPageSearchReq req, LambdaQueryChainWrapper<T> lambdaQueryChainWrapper, List<Long> topIdList,
                                                                                         Function<List<Long>, List<TxnSkuDetailVO>> skuDetailFunc,
                                                                                         Function<List<T>, Map<String, String>> staffNameFunc,
                                                                                         Function<List<T>, Map<Long, CompanyShortInfoResp>> companyInfoFunc) {
        CommonPage<ListPageResp> page = PageUtil.count(lambdaQueryChainWrapper, req.getPage(), req.getPageSize());
        if (page.getTotal() == 0) {
            return page;
        }
        List<T> infos = lambdaQueryChainWrapper.last(PageUtil.sortByUpdateTime(req.getPage(), req.getPageSize(), "id", topIdList)).list();
        List<TxnSkuDetailVO> skuDetails = skuDetailFunc.apply(infos.stream().map(T::getId).collect(Collectors.toList()));
        if (CollectionUtil.isEmpty(infos) || CollectionUtil.isEmpty(skuDetails)) {
            return page;
        }
        Map<String, String> creatorNameMap = staffNameFunc.apply(infos);
        Map<Long, CompanyShortInfoResp> companyShortInfoRespMap = companyInfoFunc.apply(infos);

        Map<Long, List<TxnSkuDetailVO>> skuDetailMap = skuDetails.stream().collect(Collectors.groupingBy(sku -> Objects.nonNull(sku.getTxnSkuDetail().getDeliverId()) ? sku.getTxnSkuDetail().getDeliverId() : sku.getTxnSkuDetail().getOrderId()));
        Function<T, ListPageResp> convertFunc = (info) -> {
            List<TxnSkuDetailVO> thisSkuDetails = skuDetailMap.get(info.getId());
            List<TxnSkuDetail> thisTxnSkuDetails = thisSkuDetails.stream().map(TxnSkuDetailVO::getTxnSkuDetail).collect(Collectors.toList());
            List<String> thisSkuImg = thisSkuDetails.stream().map(detail -> detail.getSkuInfo().getImgUrl()).collect(Collectors.toList());
            return new ListPageResp()
                    .setTop(topIdList.contains(info.getId()))
                    .setId(info.getId())
                    .setCurrency(info.getCurrency())
                    .setCategoryCount(info.getCategoryCount())
                    .setSellerCompanyInfo(companyShortInfoRespMap.get(info.getSellerId()))
                    .setBuyerCompanyInfo(companyShortInfoRespMap.get(info.getBuyerId()))
                    .setSkuImages(thisSkuImg)
                    .setTotalAmount(thisTxnSkuDetails.stream().map(TxnSkuDetail::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add).toPlainString())
                    .setStatus(info.getStatus())
                    .setCreatorName(creatorNameMap.get(buildLong2StringKey(info.getCreatorCompanyId(), info.getCreator())))
                    .setUpdateTime(info.getUpdateTime())
                    .setCreatorCompanyId(info.getCreatorCompanyId());
        };
        return PageUtil.result(infos, convertFunc, page);
    }

    public static Triple<Map<DeliverBillInfo, List<TxnFeeDetail>>, Map<Long, List<TxnFeeDetail>>, Map<DeliverBillInfo, Triple<List<TxnFeeDetail>, List<TxnFeeDetail>, List<Pair<TxnFeeDetail, TxnFeeDetail>>>>> getDeliverBillFeeDiff(Long deliverId, String currency, List<SaveDeliverReq.DeliverBillInfoReq> reqBillInfos, List<DeliverBillInfo> existBillInfos, List<TxnFeeDetail> existFeeDetails) {
        //True为已存在，False为新增
        Map<Boolean, List<SaveDeliverReq.DeliverBillInfoReq>> reqBillInfoMap = reqBillInfos.stream().collect(Collectors.groupingBy(bill -> bill.getBillId() != null));
        //更新账款
        List<SaveDeliverReq.DeliverBillInfoReq> reqExistBills = reqBillInfoMap.get(Boolean.TRUE);
        Map<Long, List<SaveDeliverReq.DeliverFeeInfo>> reqExistBillFeeMap = CollectionUtils.isEmpty(reqExistBills) ? null : reqExistBills.stream().collect(Collectors.toMap(SaveDeliverReq.DeliverBillInfoReq::getBillId, SaveDeliverReq.DeliverBillInfoReq::getFeeInfos));
        //新增账款
        List<SaveDeliverReq.DeliverBillInfoReq> reqInsertBills = reqBillInfoMap.get(Boolean.FALSE);
        //处理新增
        Map<DeliverBillInfo, List<TxnFeeDetail>> insertBillMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(reqInsertBills)) {
            reqInsertBills.forEach(reqInsertBill -> {
                Long billId = IdUtil.getSnowflakeNextId();
                BigDecimal totalAmount = reqInsertBill.getFeeInfos().stream().map(SaveDeliverReq.DeliverFeeInfo::getFeeAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
                DeliverBillInfo deliverBillInfo = new DeliverBillInfo()
                        .setId(billId)
                        .setDeliverId(deliverId)
                        .setTotalAmount(totalAmount)
                        .setSettledAmount(BigDecimal.ZERO)
                        .setCurrency(currency)
                        .setStartTime(RequestHolder.getRequestDateTime())
                        .setStatus(LyncsOdsConstant.SettlementStatus.INIT.getKey())
                        .setCreateTime(RequestHolder.getRequestDateTime())
                        .setCreator(RequestHolder.getUserId())
                        .setCreatorCompanyId(RequestHolder.getCompanyId());
                List<TxnFeeDetail> txnFeeDetails = reqInsertBill.getFeeInfos().stream().map(fee -> new TxnFeeDetail()
                        .setId(IdUtil.getSnowflakeNextId())
                        .setBillId(billId)
                        .setFeeName(fee.getFeeName())
                        .setFeeAmount(fee.getFeeAmount())
                        .setRemark(fee.getRemark())
                        .setStatus(LyncsOdsConstant.EnableStatus.ENABLE.getKey())
                        .setCreateTime(RequestHolder.getRequestDateTime())
                        .setUpdateTime(RequestHolder.getRequestDateTime())
                ).collect(Collectors.toList());
                insertBillMap.put(deliverBillInfo, txnFeeDetails);
            });
        }
        Map<Long, List<TxnFeeDetail>> delBillFeesMap = new HashMap<>();
        Map<DeliverBillInfo, Triple<List<TxnFeeDetail>, List<TxnFeeDetail>, List<Pair<TxnFeeDetail, TxnFeeDetail>>>> updateBillFeeMap = new HashMap<>();
        //数据库已存在账款
        Map<Long, List<TxnFeeDetail>> existBillFeeMap = existFeeDetails.stream().collect(Collectors.groupingBy(TxnFeeDetail::getBillId));
        Map<Long, DeliverBillInfo> billInfoMap = existBillInfos.stream().collect(Collectors.toMap(DeliverBillInfo::getId, o -> o));
        if (MapUtils.isEmpty(reqExistBillFeeMap)) {
            //整个账款删除
            delBillFeesMap = existBillFeeMap;
        } else {
            List<Long> allBillIds = CollectionUtil.addAllIfNotContains(new ArrayList<>(existBillFeeMap.keySet()), new ArrayList<>(reqExistBillFeeMap.keySet()));
            for (Long billId : allBillIds) {
                List<TxnFeeDetail> existBillFees = existBillFeeMap.getOrDefault(billId, null);
                List<SaveDeliverReq.DeliverFeeInfo> reqBillFees = reqExistBillFeeMap.getOrDefault(billId, null);
                //应为货款是无法删除的，所以应该只会走第一个if里面的逻辑
                if (CollectionUtils.isNotEmpty(existBillFees) && CollectionUtils.isNotEmpty(reqBillFees)) {
                    DeliverBillInfo deliverBillInfo = billInfoMap.get(billId);
                    BigDecimal settledAmount = deliverBillInfo.getSettledAmount();
                    //重新计算账款金额&&状态
                    BigDecimal totalAmount = reqBillFees.stream().map(SaveDeliverReq.DeliverFeeInfo::getFeeAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
                    /*if (settledAmount.compareTo(totalAmount) > 0) {
                        throw new ApiException("请勿超量结算，" + deliverBillInfo.getDeliverId() + "@" + deliverBillInfo.getId() + "的已结算总金额为" + settledAmount + "，大于当前账款的总金额：" + totalAmount);
                    }*/
                    //重新计算账款金额&&状态
                    Integer settleStatus = settledAmount.compareTo(BigDecimal.ZERO) == 0 ? LyncsOdsConstant.DeliverStatus.INIT.getKey() : settledAmount.compareTo(deliverBillInfo.getTotalAmount()) < 0 ? LyncsOdsConstant.DeliverStatus.PARTIAL_SETTLEMENT.getKey() : LyncsOdsConstant.DeliverStatus.SETTLED.getKey();
                    deliverBillInfo.setTotalAmount(totalAmount).setStatus(settleStatus).setUpdateTime(RequestHolder.getRequestDateTime());
                    getBillFeeDiff(deliverBillInfo, existBillFees, reqBillFees, updateBillFeeMap);
                } else if (CollectionUtils.isNotEmpty(existBillFees)) {
                    //delete exist bill fee
                    delBillFeesMap.put(billId, existBillFees);
                } else {
                    log.error("保存交付单时账款ID存在，但不存在于数据库中。did:{},bid:{}", deliverId, billId);
                    throw new ApiException("交付单账款已被他人更改，请稍后再试");
                    //insert req bill fee && update bill
                }

            }
        }
        return Triple.of(insertBillMap, delBillFeesMap, updateBillFeeMap);

    }

    private static void getBillFeeDiff(DeliverBillInfo deliverBillInfo, List<TxnFeeDetail> existBillFees, List<SaveDeliverReq.DeliverFeeInfo> reqBillFees, Map<DeliverBillInfo, Triple<List<TxnFeeDetail>, List<TxnFeeDetail>, List<Pair<TxnFeeDetail, TxnFeeDetail>>>> updateBillFeeMap) {
        Map<Long, TxnFeeDetail> existFeeDetailMap = existBillFees.stream().collect(Collectors.toMap(TxnFeeDetail::getId, o -> o));
        Map<Boolean, List<SaveDeliverReq.DeliverFeeInfo>> reqFeeMap = reqBillFees.stream().collect(Collectors.groupingBy(i -> i.getFeeId() != null));
        Map<Long, SaveDeliverReq.DeliverFeeInfo> reqFeeDetailMap = reqFeeMap.containsKey(Boolean.TRUE) ? reqFeeMap.get(Boolean.TRUE).stream().collect(Collectors.toMap(SaveDeliverReq.DeliverFeeInfo::getFeeId, o -> o)) : new HashMap<>();
        List<Long> allFeeIds = CollectionUtil.addAllIfNotContains(new ArrayList<>(existFeeDetailMap.keySet()), new ArrayList<>(reqFeeDetailMap.keySet()));

        List<TxnFeeDetail> insertFeeDetails = new ArrayList<>();
        List<TxnFeeDetail> delFeeIds = new ArrayList<>();
        List<Pair<TxnFeeDetail, TxnFeeDetail>> updateFeeDetails = new ArrayList<>();
        //insert new
        if (reqFeeMap.containsKey(Boolean.FALSE)) {
            List<TxnFeeDetail> newBillFees = reqFeeMap.get(Boolean.FALSE).stream().map(i -> new TxnFeeDetail()
                    .setId(IdUtil.getSnowflakeNextId())
                    .setBillId(deliverBillInfo.getId())
                    .setFeeName(i.getFeeName())
                    .setFeeAmount(i.getFeeAmount())
                    .setRemark(i.getRemark())
                    .setStatus(LyncsOdsConstant.EnableStatus.ENABLE.getKey())
                    .setCreateTime(RequestHolder.getRequestDateTime())
                    .setUpdateTime(RequestHolder.getRequestDateTime())
            ).collect(Collectors.toList());
            insertFeeDetails.addAll(newBillFees);
        }

        for (Long feeId : allFeeIds) {
            TxnFeeDetail existFeeDetail = existFeeDetailMap.getOrDefault(feeId, null);
            SaveDeliverReq.DeliverFeeInfo reqFeeDetail = reqFeeDetailMap.getOrDefault(feeId, null);
            if (existFeeDetail != null && reqFeeDetail != null) {
                updateFeeDetails.add(
                        Pair.of(existFeeDetail, new TxnFeeDetail()
                                .setId(existFeeDetail.getId())
                                .setFeeName(reqFeeDetail.getFeeName())
                                .setFeeAmount(reqFeeDetail.getFeeAmount())
                                .setRemark(reqFeeDetail.getRemark())
                                .setCreateTime(RequestHolder.getRequestDateTime())
                                .setUpdateTime(RequestHolder.getRequestDateTime()))
                );
            } else if (existFeeDetail != null) {
                delFeeIds.add(existFeeDetail);
            } else {
                log.error("fee with id, shouldn't process...");
                insertFeeDetails.add(new TxnFeeDetail()
                        .setId(IdUtil.getSnowflakeNextId())
                        .setBillId(deliverBillInfo.getId())
                        .setFeeName(reqFeeDetail.getFeeName())
                        .setFeeAmount(reqFeeDetail.getFeeAmount())
                        .setRemark(reqFeeDetail.getRemark())
                        .setStatus(LyncsOdsConstant.EnableStatus.ENABLE.getKey())
                        .setCreateTime(RequestHolder.getRequestDateTime())
                        .setUpdateTime(RequestHolder.getRequestDateTime()));
            }
        }
        updateBillFeeMap.put(deliverBillInfo, Triple.of(insertFeeDetails, delFeeIds, updateFeeDetails));
    }

    public static Triple<List<SettlementBillInfo>, List<Long>, List<SettlementBillInfo>> getSettlementBillDiff(List<SaveSettlementReq.DeliverSettleInfo> reqBillInfos, SettlementInfo settlementInfo, List<SettlementBillInfo> existBillInfos, Map<Long, BigDecimal> deliverBillAmountMap) {
        List<SettlementBillInfo> insertSettlementBillInfos = new ArrayList<>();
        List<Long> deleteDeliverBillIds = new ArrayList<>();
        List<SettlementBillInfo> updateSettlementBillInfos = new ArrayList<>();
        Map<Long, SaveSettlementReq.DeliverSettleInfo> reqBillMap = reqBillInfos.stream().collect(Collectors.toMap(SaveSettlementReq.DeliverSettleInfo::getBillId, o -> o));
        //一个结算单对应的账款ID唯一
        Map<Long, SettlementBillInfo> existBillInfoMap = existBillInfos.stream().collect(Collectors.toMap(SettlementBillInfo::getDeliverBillId, o -> o, (o1, o2) -> o2));
        List<Long> allBillIds = CollectionUtil.addAllIfNotContains(new ArrayList<>(reqBillMap.keySet()), new ArrayList<>(existBillInfoMap.keySet()));
        for (Long billId : allBillIds) {
            SettlementBillInfo existBillInfo = existBillInfoMap.getOrDefault(billId, null);
            SaveSettlementReq.DeliverSettleInfo reqBillInfo = reqBillMap.getOrDefault(billId, null);
            if (existBillInfo != null && reqBillInfo != null) {
                //update
                deliverBillAmountMap.put(existBillInfo.getDeliverBillId(), reqBillInfo.getAmount().subtract(existBillInfo.getAmount()));
                existBillInfo.setAmount(reqBillInfo.getAmount()).setRemark(reqBillInfo.getRemark());
                updateSettlementBillInfos.add(existBillInfo);
            } else if (existBillInfo != null) {
                ///delete
                deliverBillAmountMap.put(existBillInfo.getDeliverBillId(), BigDecimal.ZERO.subtract(existBillInfo.getAmount()));
                deleteDeliverBillIds.add(existBillInfo.getDeliverBillId());
            } else {
                //insert
                deliverBillAmountMap.put(reqBillInfo.getBillId(), reqBillInfo.getAmount());
                insertSettlementBillInfos.add(new SettlementBillInfo()
                        .setId(IdUtil.getSnowflakeNextId())
                        .setDeliverId(reqBillInfo.getDeliverId())
                        .setSettlementId(settlementInfo.getId())
                        .setDeliverBillId(reqBillInfo.getBillId())
                        .setAmount(reqBillInfo.getAmount())
                        .setStartTime(RequestHolder.getRequestDateTime())
                        .setRemark(reqBillInfo.getRemark())
                        .setCreateTime(RequestHolder.getRequestDateTime())
                        .setCreator(RequestHolder.getUserId())
                        .setCreatorCompanyId(RequestHolder.getCompanyId())
                );
            }
        }
        return Triple.of(insertSettlementBillInfos, deleteDeliverBillIds, updateSettlementBillInfos);
    }

    public static String buildLastSql(List<Long> topIdList, Integer page, Integer pageSize) {
        int offset = (page - 1) * pageSize;
        String limitSql = " limit " + offset + ", " + pageSize;
        if (CollectionUtil.isEmpty(topIdList)) {
            return "ORDER BY create_time DESC" + limitSql;
        }
        String idStr = topIdList.stream().map(String::valueOf).collect(Collectors.joining(LyncsOdsConstant.Common.COMMA));
        return "ORDER BY FIELD(id," + idStr + "), create_time DESC" + limitSql;
    }

    public static String buildCompanyUserKey(CompanyUserRelation relation) {
        return StringUtils.join(relation.getCompanyId(), LyncsOdsConstant.Common.WELL, relation.getUserId());
    }

    public static String buildLong2StringKey(Long o1, Long o2) {
        return StringUtils.join(o1, LyncsOdsConstant.Common.WELL, o2);
    }

    public static String buildLong2StringKeyAllowNull(Long o1, Long o2) {
        if (o1 == null && o2 == null) {
            return null;
        } else if (o1 == null) {
            return o2.toString();
        } else if (o2 == null) {
            return o1.toString();
        } else {
            return StringUtils.join(o1, LyncsOdsConstant.Common.WELL, o2);
        }
    }

    public static String buildCompanyRelationKey(CompanyContactInfo info) {
        return StringUtils.joinWith(LyncsOdsConstant.Common.WELL, info.getCompanyId(), info.getRelationId());
    }

    public static <T extends TxnCommonInfo> Map<Long, CompanyShortInfoResp> buildTxnCompanyInfo(Function<List<T>, Map<Long, CompanyShortInfoResp>> companyInfoFunc, T t) {
        List<T> list = List.of(t);
        return companyInfoFunc.apply(list);
    }

    public static <T extends TxnCommonInfo> LambdaQueryChainWrapper<T> buildLambdaQueryWrapper(LambdaQueryChainWrapper<T> lambdaQueryChainWrapper,
                                                                                               ListPageSearchReq req,
                                                                                               BiFunction<Long, Long, List<Long>> partnerFunc) {
        //如果用户已选择指定企业，则展示指定企业
        List<Long> companyIds = StringUtils.isBlank(req.getFilterCompanyId()) ? null : Stream.of(req.getFilterCompanyId().split(LyncsOdsConstant.Common.COMMA)).map(Long::parseLong).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(companyIds)) {
            //如果用户未选择指定企业，则展示有权限查看的所有企业
            companyIds = RequestHolder.isAdmin() || partnerFunc == null ? null : partnerFunc.apply(RequestHolder.getUserId(), RequestHolder.getCompanyId());
        }
        List<String> status = StringUtils.isBlank(req.getStatus()) ? null : Arrays.asList(StringUtils.split(req.getStatus(), LyncsOdsConstant.Common.COMMA));
        List<Long> finalCompanyIds = companyIds;
        return lambdaQueryChainWrapper.and(wrapper -> wrapper
                        //查询我发出的
                        .eq(LyncsOdsConstant.ContactType.SUPPLIER.getKey().equals(req.getType()), T::getSellerId, RequestHolder.getCompanyId())
                        .in(LyncsOdsConstant.ContactType.SUPPLIER.getKey().equals(req.getType()) && CollectionUtil.isNotEmpty(finalCompanyIds), T::getBuyerId, finalCompanyIds)
                        //查询我收到的
                        .or()
                        .eq(LyncsOdsConstant.ContactType.CUSTOMER.getKey().equals(req.getType()), T::getBuyerId, RequestHolder.getCompanyId())
                        .in(LyncsOdsConstant.ContactType.CUSTOMER.getKey().equals(req.getType()) && CollectionUtil.isNotEmpty(finalCompanyIds), T::getSellerId, finalCompanyIds)
                )
                .ge(StringUtils.isNotEmpty(req.getStartDate()), T::getStartDate, req.getStartDate())
                .le(StringUtils.isNotEmpty(req.getEndDate()), T::getStartDate, req.getEndDate())
                .ne(CollectionUtil.isEmpty(status), T::getStatus, -1)
                .in(CollectionUtil.isNotEmpty(status), T::getStatus, status);
    }

    public static String buildDeliverBillName(DeliverInfo del) {
        if (del == null) {
            return null;
        }
        Long companyId = RequestHolder.getCompanyIdAllowNull();
        Long textId;
        if (del.getSellerId().equals(companyId)) {
            textId = LanguageConstant.BILL_RECEIVE;
        } else if (del.getBuyerId().equals(companyId)) {
            textId = LanguageConstant.BILL_PAY;
        } else {
            textId = LanguageConstant.BILL_DEFAULT;
        }
        return SpringUtils.getBean(CmsTextService.class).getContent(textId, Map.of("deliverName", del.getDeliverName()));
    }

    public static void handlerCompanyRelationChange(Long currentCompanyId, Long relationCompanyId, Long initCompanyId, Boolean release,
                                                    LambdaUpdateChainWrapper<OrderInfo> orderBuyerWrapper,
                                                    LambdaUpdateChainWrapper<OrderInfo> orderSellerWrapper,
                                                    LambdaUpdateChainWrapper<DeliverInfo> deliverBuyerWrapper,
                                                    LambdaUpdateChainWrapper<DeliverInfo> deliverSellerWrapper,
                                                    LambdaUpdateChainWrapper<SettlementInfo> settlementBuyerWrapper,
                                                    LambdaUpdateChainWrapper<SettlementInfo> settlementSellerWrapper) {
        if ((!ObjectUtils.allNotNull(relationCompanyId, initCompanyId)) || Objects.equals(relationCompanyId, initCompanyId)) {
            return;
        }
        handlerChangeOnce(currentCompanyId, relationCompanyId, initCompanyId, release, orderBuyerWrapper.setEntityClass(OrderInfo.class), orderSellerWrapper.setEntityClass(OrderInfo.class));
        handlerChangeOnce(currentCompanyId, relationCompanyId, initCompanyId, release, deliverBuyerWrapper.setEntityClass(DeliverInfo.class), deliverSellerWrapper.setEntityClass(DeliverInfo.class));
        handlerChangeOnce(currentCompanyId, relationCompanyId, initCompanyId, release, settlementBuyerWrapper.setEntityClass(SettlementInfo.class), settlementSellerWrapper.setEntityClass(SettlementInfo.class));
    }

    private static <T extends TxnCommonInfo> void handlerChangeOnce(Long currentCompanyId, Long relationCompanyId, Long initCompanyId, Boolean release,
                                                                    LambdaUpdateChainWrapper<T> buyerWrapper,
                                                                    LambdaUpdateChainWrapper<T> sellerWrapper) {
        buyerWrapper.eq(T::getCreatorCompanyId, currentCompanyId);
        sellerWrapper.eq(T::getCreatorCompanyId, currentCompanyId);
        //解除关联，A => B
        if (release) {
            sellerWrapper.eq(T::getSellerId, relationCompanyId).set(T::getSellerId, initCompanyId).update();
            buyerWrapper.eq(T::getBuyerId, relationCompanyId).set(T::getBuyerId, initCompanyId).update();
        } else {
            //B类好友转A类，B => A
            sellerWrapper.eq(T::getSellerId, initCompanyId).set(T::getSellerId, relationCompanyId).update();
            buyerWrapper.eq(T::getBuyerId, initCompanyId).set(T::getBuyerId, relationCompanyId).update();
        }
    }

    public static String getTagInfo(String existTagStr, TagInfo tagInfo, Boolean release) {
        List<TagInfo> tagInfos = JSONArray.parseArray(existTagStr, TagInfo.class);
        tagInfos = CollectionUtils.isEmpty(tagInfos) ? new ArrayList<>() : tagInfos;
        //当前tag已存在或者有优先级更高的tag则不新增
        boolean hasTag = tagInfos.stream().anyMatch(tag -> tag.getCode().equals(tagInfo.getCode()) || (tag.getGroup().equals(tagInfo.getGroup()) && tag.getPriority() > tagInfo.getPriority()));

        if (release && hasTag) {
            tagInfos.remove(tagInfo);
        } else if (!release && !hasTag) {
            tagInfos = List.of(tagInfo);
        }
        return JSONObject.toJSONString(tagInfos, SerializerFeature.WriteMapNullValue);
    }

    public static <T extends TxnCommonInfo> void notify(T info,
                                                        Function<List<Long>, Map<Long, CompanyInfo>> companyFunc,
                                                        BiConsumer<CompanyInfo, CompanyInfo> emailNotifyFunc,
                                                        BiConsumer<CompanyInfo, CompanyInfo> smsNotifyFunc) {
        if (info.getStatus() == 0) {
            log.info("do not send sms/email for draft ticket...ID:{}", info.getId());
        }
        Long creatorCompanyId = info.getCreatorCompanyId();
        Long buyerId = info.getBuyerId();
        Long sellerId = info.getSellerId();
        List<Long> companyIds = Stream.of(creatorCompanyId, buyerId, sellerId).distinct().collect(Collectors.toList());
        Map<Long, CompanyInfo> availableCompanyInfoMap = companyFunc.apply(companyIds);
        CompanyInfo creatorCompanyInfo = availableCompanyInfoMap.get(creatorCompanyId);
        CompanyInfo buyerCompanyInfo = availableCompanyInfoMap.get(info.getBuyerId());
        CompanyInfo sellerCompanyInfo = availableCompanyInfoMap.get(info.getSellerId());
        if (!creatorCompanyId.equals(info.getBuyerId())) {
            emailNotifyFunc.accept(creatorCompanyInfo, buyerCompanyInfo);
            smsNotifyFunc.accept(creatorCompanyInfo, buyerCompanyInfo);
        }
        if (!creatorCompanyId.equals(info.getSellerId())) {
            emailNotifyFunc.accept(creatorCompanyInfo, sellerCompanyInfo);
            smsNotifyFunc.accept(creatorCompanyInfo, sellerCompanyInfo);
        }
    }

    public static <T extends TxnCommonInfo> Integer count(LambdaQueryChainWrapper<T> lambdaQueryChainWrapper) {
        Long companyId = RequestHolder.getCompanyId();
        return lambdaQueryChainWrapper.ne(T::getStatus, -1)
                .and(wrapper -> wrapper
                        //查询我发出的
                        .eq(T::getSellerId, companyId)
                        //查询我收到的
                        .or()
                        .eq(T::getBuyerId, companyId)
                ).count();
    }

    public static <T extends TxnCommonInfo> String buildTicketName(LambdaQueryChainWrapper<T> lambdaQueryChainWrapper,
                                                                   Function<List<T>, Map<Long, CompanyShortInfoResp>> companyInfoFunc,
                                                                   LyncsOdsConstant.TxnType type, T t,
                                                                   LocalDate reqDate, LocalDate dbDate,
                                                                   String reqName, String dbName) {
        //用户自己已经设置了单据名称
        if (StringUtils.isNotBlank(reqName)) {
            return reqName;
        }
        //未修改日期名称不变
        /*if (reqDate.equals(dbDate)) {
            return dbName;
        }*/
        String shortName = companyInfoFunc.apply(List.of(t)).get(t.getBuyerId()).getShortName();
        //单据构成 日期 + 顺位码
        int count = lambdaQueryChainWrapper.eq(T::getStartDate, reqDate).eq(T::getCreatorCompanyId, RequestHolder.getCompanyId()).count() + 1;
        String suffix = count > 9 ? Integer.toString(count) : StringUtils.join("0", count);
        return StringUtils.join(type.getPrefix(), ".", shortName, DTF1.format(reqDate), suffix);
    }

}
